/*    
 * Copyright 2013 Brandon O'Toole
 * 
 * This file is part of EdgeSleuth.
 *
 * EdgeSleuth is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EdgeSleuth is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EdgeSleuth.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.IndieRayDev.EdgeSleuth;

import java.util.ConcurrentModificationException;
import java.util.HashSet;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class DrawImage extends SurfaceView implements SurfaceHolder.Callback{
	class ImageThread extends Thread{
		
		private boolean mRun = false;
		private int[] mPixelColorArray;
		private SurfaceHolder mSurfaceHolder;
		private Canvas c = null;
		
		ImageThread(SurfaceHolder surfaceHolder, Handler handler){
			mSurfaceHolder = surfaceHolder;
		}
		
		private Rect getUpdateBounds(HashSet<Point> pointSet, Bitmap bitmap, int radius){
			if( pointSet.size() < 3 ){
				return null;
			} else {
				return findUpdateBounds(pointSet, bitmap, radius);
			}
		}
		
		private Rect findUpdateBounds(HashSet<Point> inPointSet, Bitmap inBitmap, int inRadius){
			int l = inBitmap.getWidth();
			int r = 0;
			int t = inBitmap.getHeight();
			int b = 0;
			
			for( Point p : inPointSet ){
				l = Math.min(p.x, l);
				r = Math.max(p.x, r);
				t = Math.min(p.y, t);
				b = Math.max(p.y, b);
			}
			
			return new Rect(l-2*inRadius, t-2*inRadius, r+2*inRadius, b+2*inRadius);
		}
		
		private void uncoverImage(Point p, Bitmap bitmap, Bitmap overlay, int r, float scale){;
			int px = Math.max( 0, Math.min( 
					(int)((p.x-r)*scale), 
					bitmap.getWidth()-(int)((r+r)*scale)) 
					);
			int py = Math.max( 0, Math.min( 
					(int)((p.y-r)*scale), 
					bitmap.getHeight()-(int)((r+r)*scale))
					);
			bitmap.setPixels(
					mPixelColorArray, 
					px + py*bitmap.getWidth(), 
					bitmap.getWidth(), 
					px, 
					py, 
					(int)((r+r)*scale), 
					(int)((r+r)*scale));
		}
		
		@Override
		public void run(){
			mPixelColorArray = new int[mBitmap.getWidth()*mBitmap.getHeight()]; 
			mBotBitmap.getPixels(
					mPixelColorArray, 
					0, 
					mBotBitmap.getWidth(), 
					0, 
					0, 
					mBotBitmap.getWidth(), 
					mBotBitmap.getHeight());
			
			synchronized (mSurfaceHolder) {
				while (mRun) {
	        		try {
	        			HashSet<Point> alphaThreadSet = new HashSet<Point>(alphaSet);
	    				c = mSurfaceHolder.lockCanvas(
	    						getUpdateBounds(alphaThreadSet, mBitmap, 50));
	        			if(c != null){
	        				prepareCanvas(c);
		        			mBitmap.setDensity((int)(mTopBitmap.getWidth()));
		        			c.drawBitmap(mBitmap, 0, 0, null);
		        			uncoverPoints(alphaThreadSet);
	        			}
	        		} catch (ConcurrentModificationException e) {
	        			// TODO have better logging of the concurrent modification Exception
	        			Log.e("TAG", "HERE IS THE CME");
    				}finally {
	        			// if an exception is thrown, button up the Surface
	        			if (c != null) {
	        				mSurfaceHolder.unlockCanvasAndPost(c);
	        				c = null;
	        			}
	        		} 
    			}
        	}
		}
		
		private void prepareCanvas(Canvas canvas){
			canvas.setDensity(mWidth);
			canvas.drawColor(0xFFFFFFFF);
		}
		
		private void uncoverPoints(HashSet<Point> inAlphaThreadSet){
			for(Point p : inAlphaThreadSet){
				uncoverImage(
						p, 
						mBitmap, 
						mBotBitmap, 
						(int)mDensity/10, 
						((float)mTopBitmap.getWidth()/mWidth) );
				alphaSet.remove(p);
			}
			inAlphaThreadSet.clear();
		}

	
		public void setRunning(boolean b){
			mRun = b;
		}
		
	}

	private Bitmap mBitmap, mTopBitmap, mBotBitmap;
	private ImageThread mThread;
	private int mWidth; 
	private float mDensity;
	private HashSet<Point> alphaSet;
	
    public ImageThread getThread(){
    	return mThread;
    }
    
    public void addAlphaToSet(int x, int y){
    	alphaSet.add(new Point(x, y));
    }
    public void addAlphaToSet(float x, float y){
    	alphaSet.add(new Point((int) x, (int) y));
    }
    
    public void setBitmaps(Bitmap topBitmap, Bitmap botBitmap){
    	tryToRecycleBitmap(mTopBitmap);
    	mTopBitmap = topBitmap.copy(topBitmap.getConfig(), false);
    	
    	tryToRecycleBitmap(mBotBitmap);
    	mBotBitmap = botBitmap.copy(botBitmap.getConfig(), false);
    	
    	tryToRecycleBitmap(mBitmap);
    	mBitmap = mTopBitmap.copy(mTopBitmap.getConfig(), true);
    }
    
    public void tryToRecycleBitmap(Bitmap globalBitmap){
    	if(globalBitmap != null){
    		globalBitmap.recycle();
    	}
    }
    
    public void setScreenSize(Activity activity){
    	mWidth = activity.getWindowManager().getDefaultDisplay().getWidth();
    	mDensity = activity.getResources().getDisplayMetrics().xdpi;
    }

	public DrawImage(Context context, AttributeSet attrs) {
		super(context, attrs);
		
		SurfaceHolder holder = getHolder();
        holder.addCallback(this);
        alphaSet = new HashSet<Point>();

        mThread = new ImageThread(holder, new Handler());
	}
    
    public void surfaceCreated(SurfaceHolder holder){
        if (mThread == null){
        	mThread = new ImageThread(holder, new Handler());
        }
        
    	mThread.setRunning(true);
    	mThread.setPriority(Thread.MIN_PRIORITY);
    	mThread.start();
    	
    }  
    
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    	mWidth = width;
    }
    
    public void surfaceDestroyed(SurfaceHolder holder){
        // we have to tell thread to shut down & wait for it to finish, or else
        // it might touch the Surface after we return and explode
        boolean retry = true;
        mThread.setRunning(false);
        while (retry) {
            try {
                mThread.join();
                retry = false;
            } catch (InterruptedException e) {
            	// TODO justify this empty catch
            	// ignore the error until it goes away... haha
            }
        }
        mThread = null;
    }	
}
